using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Reflection;

using eXamineBusiness.System.Exceptions;
using eXamineBusiness.SystemDescription.Data;
using eXamineBusiness.SystemDescription.Events;
using eXamineBusiness.SystemDescription.Exceptions;
using log4net;
using QuickGraph;
using QuickGraph.Algorithms.TopologicalSort;

namespace eXamineBusiness.SystemDescription
{
    public abstract class Description : DescriptionItem
    {
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        public enum ConnectionsType
        {
            InConnections,
            OutConnections,
            Both
        }

        #region Variables
        private readonly NameCollection _names;
        private readonly GenericCollection<Description> _childs;
        private readonly GenericCollection<Connection> _connections;
        private readonly ReceivedEventCollection _receivedEvents;
        private readonly GenericCollection<ThrownEvent> _thrownEvents;
        private readonly GenericCollection<FailureMode> _failureModes;
        private readonly Position _position;
        #endregion

        #region Constructors

        protected Description(String name)
            : base(name)
        {
            Contract.Requires(name != null);
            this._names = new NameCollection();
            this._childs = new GenericCollection<Description>();
            this._connections = new GenericCollection<Connection>();
            this._receivedEvents = new ReceivedEventCollection();
            this._thrownEvents = new GenericCollection<ThrownEvent>();
            this._failureModes = new GenericCollection<FailureMode>();
            this._position = new Position(0, 0);
            this.Parent = this;
            this.RootParent = this;
        }
        #endregion

        #region Event
        public event EventHandler<DescriptionItemEventArgs> SubItemAdded;
        private void OnSubItemAdded (DescriptionItem item)
        {
            if (this.SubItemAdded != null)
            {
                this.SubItemAdded.Invoke(this, new DescriptionItemEventArgs(item));
            }
        }
        public event EventHandler<DescriptionItemEventArgs> SubItemRemoved;
        private void OnSubItemRemoved(DescriptionItem item)
        {
            if (this.SubItemRemoved != null)
            {
                this.SubItemRemoved.Invoke(this, new DescriptionItemEventArgs(item));
            }
        }
        #endregion

        #region Properties
        public Position Position {
            get { return this._position; }
        }
        public GenericCollection<Description> Childs
        {
            get { return _childs; }
        }
        public GenericCollection<Connection> Connections
        {
            get { return _connections; }
        }
        public ReceivedEventCollection ReceivedEvents
        {
            get { return _receivedEvents; }
        }
        public GenericCollection<ThrownEvent> ThrownEvents
        {
            get { return _thrownEvents; }
        }
        public GenericCollection<FailureMode> FailureModes
        {
            get { return _failureModes; }
        }
        #endregion

        #region API
        [Pure]
        public bool Contains(DescriptionItem child)
        {
            if (child == null) return false;
            if (child is Description) return Contains((Description)child);
            if (child is Connection) return Contains((Connection)child);
            if (child is ThrownEvent) return Contains((ThrownEvent)child);
            if (child is ReceivedEvent) return Contains((ReceivedEvent)child);
            if (child is FailureMode) return Contains((FailureMode)child);
            return false;
        }
        [Pure]
        public bool Contains (Description child)
        {
            if (child == null) return false;
            return this._childs.Contains(child);
        }
        [Pure]
        public bool Contains(FailureMode child)
        {
            if (child == null) return false;
            return this._failureModes.Contains(child);
        }
        [Pure]
        public bool Contains(ReceivedEvent child)
        {
            if (child == null) return false;
            return this._receivedEvents.Contains(child);
        }
        [Pure]
        public bool Contains(ThrownEvent child)
        {
            if (child == null) return false;
            return this._thrownEvents.Contains(child);
        }
        [Pure]
        public bool Contains(Connection child)
        {
            if (child == null) return false;
            return this._connections.Contains(child);
        }
        public NameCollection Names
        {
            get { return this._names; }
        }
        public void Add (Description child)
        {
            Contract.Requires(child != null);
            Contract.Ensures(Contains(child));
            Contract.Ensures(child.Parent.Equals(this));
            Contract.Ensures(child.RootParent.Equals(RootParent));
            Names.Add(child.Name, child);
            child.Parent = this;
            child.RootParent = this.RootParent;
            Childs.Add(child);
            this.OnSubItemAdded(child);
        }
        public void Add (Connection connection)
        {
            Contract.Requires(connection != null);
            Contract.Ensures(connection.Parent.Equals(this));
            Contract.Ensures(Contains(connection));
            foreach (Connection conn in Connections)
            {
                if (conn.Source.Equals(connection.Source) &&
                    conn.Target.Equals(connection.Target))
                    throw new ConnectionAlreadyExistsException(true);


                if (conn.Source.Equals(connection.Target) &&
                    conn.Target.Equals(connection.Source))
                    throw new ConnectionAlreadyExistsException(false);
            }
            Names.Add(connection.Name, connection);
            connection.Parent = this;
            connection.RootParent = this.RootParent;
            Connections.Add(connection);
            this.OnSubItemAdded(connection);
        }
        public void Add (FailureMode mode)
        {
            Contract.Requires(mode != null);
            Contract.Ensures(mode.Parent.Equals(this));
            Contract.Ensures(Contains(mode));
            Names.Add(mode.Name, mode);
            mode.Parent = this;
            mode.RootParent = this.RootParent;
            FailureModes.Add(mode);
            this.OnSubItemAdded(mode);
        }
        public void Add (ReceivedEvent receivedEvent)
        {
            Contract.Requires(receivedEvent != null);
            Contract.Ensures(receivedEvent.Parent.Equals(this));
            Contract.Ensures(Contains(receivedEvent));
            Names.Add(receivedEvent.Name, receivedEvent);
            receivedEvent.Parent = this;
            receivedEvent.RootParent = this.RootParent;
            ReceivedEvents.Add(receivedEvent);
            this.OnSubItemAdded(receivedEvent);
        }
        public void Add(ThrownEvent thrownEvent)
        {
            Contract.Requires(thrownEvent != null);
            Contract.Ensures(Contains(thrownEvent));
            Contract.Ensures(thrownEvent.Parent.Equals(this));
            Names.Add(thrownEvent.Name, thrownEvent);
            thrownEvent.Parent = this;
            thrownEvent.RootParent = this.RootParent;
            ThrownEvents.Add(thrownEvent);
            this.OnSubItemAdded(thrownEvent);
        }

        private BidirectionalGraph<String, Edge<String>> GetInferedEventGraph ()
        {
            BidirectionalGraph<String, Edge<String>> graph = new BidirectionalGraph<string, Edge<string>>();
            Queue<Description> queue = new Queue<Description>();
            queue.Enqueue(this.RootParent);
            while (queue.Count > 0)
            {
                Description desc = queue.Dequeue();
                foreach (FailureMode failureMode in desc.FailureModes)
                {
                    if (!graph.ContainsVertex(failureMode.UniqueName))
                        graph.AddVertex(failureMode.UniqueName);
                    if (failureMode.HasThrownEvent)
                    {
                        if (!graph.ContainsVertex(failureMode.ThrownEvent.UniqueName))
                            graph.AddVertex(failureMode.ThrownEvent.UniqueName);
                        graph.AddEdge(new Edge<string>(failureMode.UniqueName, failureMode.ThrownEvent.UniqueName));
                    }
                    if (!failureMode.ReceivedEvents.Empty)
                    {
                        foreach (ReceivedEvent receivedEvent in failureMode.ReceivedEvents)
                        {
                            if (!graph.ContainsVertex(receivedEvent.UniqueName))
                                graph.AddVertex(receivedEvent.UniqueName);
                            graph.AddEdge(new Edge<string>(receivedEvent.UniqueName, failureMode.UniqueName));
                        }
                    }
                }
                foreach (Description description in desc.Childs)
                    queue.Enqueue(description);
            }
            return graph;
        }

        [Pure]
        public bool isDAG (Event r, FailureMode f)
        {
            BidirectionalGraph<String, Edge<String>> graph = this.GetInferedEventGraph();
            if (!graph.ContainsVertex(r.UniqueName))
                graph.AddVertex(r.UniqueName);
            if (!graph.ContainsVertex(f.UniqueName))
                graph.AddVertex(f.UniqueName);
            graph.AddEdge(new Edge<string>(r.UniqueName, f.UniqueName));
            TopologicalSortAlgorithm<String, Edge<String>> topologicalSortAlgorithm =
                    new TopologicalSortAlgorithm<String, Edge<String>>(graph);

            try
            {
                topologicalSortAlgorithm.Compute();
                return true;
            }
            catch (NonAcyclicGraphException ex)
            {
                return false;
            }
        }

        public void Associate (ReceivedEvent receivedEvent, FailureMode mode)
        {
            Contract.Requires(mode != null);
            Contract.Requires(receivedEvent != null);
            /*
	        f.received in (
		        // 2.1
		        (f.received & ReceivedEvent) +
		        // 2.2
		        s.getThrownEvents[s.getChilds[p]] +
		        // 2.3
		        s.getThrownEvents[s.getSameLevel[p]]
	        )
            */
            Contract.Requires(
                // 2.1
                (!(receivedEvent is ReceivedThrownEvent) && 
                receivedEvent.HasParent &&
                mode.HasParent &&
                (receivedEvent.Parent.DeepEquals(mode.Parent))) ||
                // 2.2
                ((receivedEvent is ReceivedThrownEvent) &&
                receivedEvent.HasParent &&
                mode.HasParent &&
                receivedEvent.Parent.HasParent &&
                (((ReceivedThrownEvent)receivedEvent).ThrownEvent.Parent.Parent.DeepEquals(mode.Parent))) ||
                // 2.3
                ((receivedEvent is ReceivedThrownEvent) &&
                receivedEvent.HasParent &&
                mode.HasParent &&
                receivedEvent.Parent.HasParent &&
                mode.Parent.HasParent &&
                (((ReceivedThrownEvent)receivedEvent).ThrownEvent.Parent.Parent.DeepEquals(mode.Parent.Parent)))
                );
            //	isDag[s, f]
            Contract.Ensures(isDAG(receivedEvent, mode));
            Contract.Ensures(mode.Contains(receivedEvent));
            mode.Add(receivedEvent);
        }
        public void Deassociate (ReceivedEvent receivedEvent, FailureMode mode)
        {
            Contract.Requires(mode != null);
            Contract.Requires(receivedEvent != null);
            Contract.Ensures(!mode.Contains(receivedEvent));
            mode.Remove(receivedEvent);
        }
        public void Associate(ThrownEvent thrownEvent, FailureMode mode)
        {
            Contract.Requires(mode != null);
            Contract.Requires(thrownEvent != null);
            Contract.Requires(
                thrownEvent.HasParent &&
                mode.HasParent &&
                thrownEvent.Parent.DeepEquals(mode.Parent)
                );
            //	isDag[s, f]
            Contract.Ensures(isDAG(thrownEvent, mode));
            Contract.Ensures(mode.Contains(thrownEvent));
            mode.Set(thrownEvent);
        }
        public void Deassociate (ThrownEvent thrownEvent, FailureMode mode)
        {
            Contract.Requires(mode != null);
            Contract.Requires(thrownEvent != null);
            Contract.Ensures(!mode.Contains(thrownEvent));
            mode.Unset(thrownEvent);
        }
        public void Remove (FailureMode mode)
        {
            Contract.Requires(mode != null);
            Contract.Ensures(!Contains(mode));
            List<ReceivedEvent> events = new List<ReceivedEvent>(mode.ReceivedEvents);
            foreach (ReceivedEvent receivedEvent in events)
                this.Deassociate(receivedEvent, mode);
            if (mode.HasThrownEvent)
                this.Deassociate(mode.ThrownEvent, mode);
            this.FailureModes.Remove(mode);
            this.Names.Remove(mode.Name);
            this.OnSubItemRemoved(mode);
        }
        public void Remove (ThrownEvent thrownEvent)
        {
            Contract.Requires(thrownEvent != null);
            Contract.Ensures(!Contains(thrownEvent));
            foreach (FailureMode failureMode in this.FailureModes)
            {
                if (failureMode.HasThrownEvent)
                    if (failureMode.ThrownEvent.Equals(thrownEvent))
                        this.Deassociate(thrownEvent, failureMode);
            }
            this.ThrownEvents.Remove(thrownEvent);
            this.Names.Remove(thrownEvent.Name);
            this.OnSubItemRemoved(thrownEvent);
        }
        public void Remove (ReceivedEvent receivedEvent)
        {
            Contract.Requires(receivedEvent != null);
            Contract.Ensures(!Contains(receivedEvent));
            foreach (FailureMode failureMode in this.FailureModes)
            {
                if (failureMode.ReceivedEvents.Contains(receivedEvent)) {
                    this.Deassociate(receivedEvent, failureMode);
                }
            }
            this.ReceivedEvents.Remove(receivedEvent);
            this.Names.Remove(receivedEvent.Name);
            this.OnSubItemRemoved(receivedEvent);
        }
        public void Remove (Connection connection)
        {
            Contract.Requires(connection != null);
            Contract.Ensures(!Contains(connection));
            this.RemoveConnections(connection.Source, connection.Target);
            if (connection.TwoWay) this.RemoveConnections(connection.Target, connection.Source);
            this.Connections.Remove(connection);
            this.Names.Remove(connection.Name);
            this.OnSubItemRemoved(connection);
        }
        public void Remove (Description child)
        {
            Contract.Requires(child != null);
            Contract.Ensures(!Contains(child));
            child.RemoveAllData();
            List<Connection> list = new List<Connection>(this.Connections);
            foreach (Connection connection in list)
            {
                if (connection.Target.Equals(child))
                    this.Remove(connection);
                else if (connection.Source.Equals(child))
                    this.Remove(connection);
            }
            this.Childs.Remove(child);
            this.Names.Remove(child.Name);
            this.OnSubItemRemoved(child);
        }
        #endregion

        #region Auxiliar
        private void RemoveAllData ()
        {
            foreach (Description description in this.Childs)
                description.RemoveAllData();
            List<ReceivedEvent> receivedEvents = new List<ReceivedEvent>(this.ReceivedEvents);
            foreach (ReceivedEvent receivedEvent in receivedEvents)
                this.Remove(receivedEvent);
            List<ThrownEvent> thrownEvents = new List<ThrownEvent>(this.ThrownEvents);
            foreach (ThrownEvent thrownEvent in thrownEvents)
                this.Remove(thrownEvent);
            List<FailureMode> failureModes = new List<FailureMode>(this.FailureModes);
            foreach (FailureMode failureMode in failureModes)
                this.Remove(failureMode);
            List<Connection> connections = new List<Connection>(this.Connections);
            foreach (Connection connection in connections)
                this.Remove(connection);
            this.Childs.Clear();
        }
        private void RemoveConnections (Description source, Description target)
        {
            foreach (FailureMode failureMode in source.FailureModes)
            {
                if (failureMode.HasThrownEvent)
                {
                    foreach (FailureMode mode in target.FailureModes)
                    {
                        foreach (ReceivedEvent receivedEvent in new List<ReceivedEvent>(mode.ReceivedEvents))
                        {
                            if (receivedEvent.DeepEquals(failureMode.ThrownEvent))
                                mode.ReceivedEvents.Remove(receivedEvent);
                        }
                    }
                }
            }
        }
        #endregion

        public void Remove(DescriptionItem descriptionItem)
        {
            Contract.Requires(descriptionItem != null);
            Contract.Ensures(!Contains(descriptionItem));
            if (descriptionItem is Description)
                this.Remove((Description) descriptionItem);
            else if (descriptionItem is Connection)
                this.Remove((Connection) descriptionItem);
            else if (descriptionItem is ThrownEvent)
                this.Remove((ThrownEvent) descriptionItem);
            else if (descriptionItem is ReceivedEvent)
                this.Remove((ReceivedEvent) descriptionItem);
            else if (descriptionItem is FailureMode)
                this.Remove((FailureMode) descriptionItem);
        }
    }
}